﻿using Castle.DynamicProxy;
using Microsoft.AspNetCore.Http;
using MyCompany.MyProject.Common;
using MyCompany.MyProject.Common.LogHelper;
using Newtonsoft.Json;
using System.Reflection;

#nullable disable

namespace MyCompany.MyProject.Extensions.AOP;

/// <summary>
/// 记录日志 AOP 
/// </summary>
public class LogAop : IInterceptor
{
    private readonly IHttpContextAccessor accessor;

    public LogAop(IHttpContextAccessor accessor)
    {
        this.accessor = accessor;
    }

    public void Intercept(IInvocation invocation)
    {
        var userName = accessor.HttpContext?.User?.Identity?.Name;
        string json;
        try
        {
            json = JsonConvert.SerializeObject(invocation.Arguments);
        }
        catch (Exception ex)
        {
            json = "无法序列化，可能是兰姆达表达式等原因造成的，按框架代码优化" + ex.Message;
        }

        var startTime = DateTime.Now;
        AOPLogInfo logInfo = new AOPLogInfo()
        {
            RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"),
            OpUserName = userName!,
            RequestMethodName = invocation.Method.Name,
            RequestParamsData = string.Join(",", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()),
            ResponseJsonData = json,
        };
        try
        {
            invocation.Proceed();

            // 异步获取异常，先执行
            if (IsAsyncMethod(invocation.Method))
            {
                if (invocation.Method.ReturnType == typeof(Task))
                {
                    invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
                        (Task)invocation.ReturnValue,
                        async () => await SuccessAction(invocation, logInfo, startTime),
                        ex => LogEx(ex, logInfo));
                }
                else
                {
                    invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
                        invocation.Method.ReturnType.GenericTypeArguments[0],
                        invocation.ReturnValue,
                        async (o) => await SuccessAction(invocation, logInfo, startTime, o),
                        ex => LogEx(ex, logInfo));
                }
            }
            else
            {
                // 同步
                string jsonResult;
                try
                {
                    jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue);
                }
                catch (Exception ex)
                {
                    jsonResult = "无法序列化，可能是兰姆达表达式等原因造成，按照框架优化代码" + ex.ToString();
                }

                var type = invocation.Method.ReturnType;
                var resultProperty = type.GetProperty("Result");
                DateTime endTime = DateTime.Now;
                string responseTime = (endTime - startTime).Microseconds.ToString();
                logInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff");
                logInfo.ResponseIntervalTime = responseTime + "ms";
                logInfo.ResponseJsonData = jsonResult;
                Parallel.For(0, 1, e => LogLock.OutLogAOP("AOPLog", accessor.HttpContext.TraceIdentifier, new string[] { logInfo.GetType().ToString(), JsonConvert.SerializeObject(logInfo) }));
            }
        }
        catch (Exception ex)
        {
            LogEx(ex, logInfo);
            throw;
        }

        // TODO Hub 发送消息功能
    }

    /// <summary>
    /// 记录异常日志
    /// </summary>
    /// <param name="ex"></param>
    /// <param name="logInfo"></param>
    private void LogEx(Exception ex, AOPLogInfo logInfo)
    {
        if (ex != null)
        {
            // 执行的 server 中收录异常
            AOPLogExInfo logEx = new AOPLogExInfo()
            {
                ExMessage = ex.Message,
                InnerException = "InnerException - 内部异常:\r\n" + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + "\r\nStackTrace - 堆栈跟踪:\r\n" + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()),
                AOPLogInfo = logInfo,
            };
            // 异常日志丽有详细堆栈信息
            Parallel.For(0, 1, e =>
            {
                LogLock.OutLogAOP("AOPLogEx", accessor.HttpContext?.TraceIdentifier!, new string[] { logEx.GetType().ToString(), JsonConvert.SerializeObject(logEx) });
            });
        }
    }


    public static bool IsAsyncMethod(MethodInfo method)
    {
        return method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>));
    }

    /// <summary>
    /// 执行成功方法
    /// </summary>
    /// <param name="invocation"></param>
    /// <param name="logInfo"></param>
    /// <param name="startTime"></param>
    /// <param name="o"></param>
    /// <returns></returns>
    private async Task SuccessAction(IInvocation invocation, AOPLogInfo logInfo, DateTime startTime, object o = null)
    {
        DateTime endTime = DateTime.Now;
        string responseTime = (endTime - startTime).Microseconds.ToString();
        logInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff");
        logInfo.ResponseIntervalTime = responseTime + "ms";
        logInfo.ResponseJsonData = JsonConvert.SerializeObject(o);

        await Task.Run(() =>
        {
            Parallel.For(0, 1, e =>
            {
                LogLock.OutLogAOP("AOPLog", accessor.HttpContext?.TraceIdentifier!, new string[] { logInfo.GetType().ToString(), JsonConvert.SerializeObject(logInfo) });
            });
        });

    }

}

internal static class InternalAsyncHelper
{
    public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
    {
        Exception exception = null;
        try
        {
            await actualReturnValue;
            await postAction();
        }
        catch (Exception ex)
        {
            exception = ex;
        }
        finally
        {
            finalAction(exception);
        }
    }

    public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<object, Task> postAction, Action<Exception> finalAction)
    {
        Exception exception = null;
        try
        {
            var result = await actualReturnValue;
            await postAction(result);
            return result;
        }
        catch (Exception ex)
        {
            exception = ex;
            throw;
        }
        finally
        {
            finalAction(exception);
        }
    }

    public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<object, Task> action, Action<Exception> finalAction)
    {
        return typeof(InternalAsyncHelper).GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
            .MakeGenericMethod(taskReturnType)
            .Invoke(null, new object[] { actualReturnValue, action, finalAction });
    }
}